package BNS.graphs;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.util.Enumeration;

import javax.swing.JPanel;


/**
 * This code is useful for laying out simple graphs. It attempts to spread out
 * nodes for better visualization, and is successful on stars and cycles. It
 * tries to keep nodes from lying too close to non-incident edges. It has no
 * intrinsic ability to detect or reveal planarity.
 * 
 * Ideas for improvement:
 * <ul>
 * <li>define congestion around node in terms of distance to edges as well as
 * distance to other nodes. Also include number of edge crossings.</li>
 * <li>calculate congestion in each position of a ring around each node and
 * move toward the best.</li>
 * <li>add a speedstroke to select next node.</li>
 * </ul>
 */
public class GraphPanel extends JPanel {

	public static final Font FONT=new Font("Times New Roman",Font.BOLD,18);


	protected Graph graph;
	protected ColorMap render;

	// The following line stops a compiler warning:
	private static final long serialVersionUID=0L;
	protected int nodeRadius=25;
	protected boolean creating;
	protected String waitMessage;

	/**
	 * constructor that prepares to display a Graph for player viewing. A colormap
	 * must be provided to indicate which colours should be displayed for each of
	 * the values of Node.color (which is a mere integer). For some games, it may
	 * be important to make sure that one player's apparent colors cannot be
	 * identified with another player's apparent colors; but if no such
	 * obfuscation is required, supply the identity ColorMap instead.
	 * 
	 * @param cm
	 *          the ColorMap used to interpret Node.color
	 */
	public GraphPanel(ColorMap cm) {
		render=cm;
		creating=false;
		waitMessage="panel created";
	}

	/** Constructs a GraphPanel with the identity ColorMap */
	public GraphPanel() {
		this(ColorMap.identity());
	}

	/**
	 * Used to set the graph at run time (not at time of creation).
	 * 
	 * @param gr
	 *          the Graph that will be displayed.
	 */
	public void init(Graph gr) {
		// System.err.println("GraphPanel init");
		if(gr==null) {
			System.err.println("\tnull graph");
			return;
		}
		graph=gr;
		// graph.prettyPrint(System.err);
		// System.err.println("graph has "+graph.numNodes()+" nodes &
		// "+graph.numEdges()+" edges");
		synchronized(this) {
			waitMessage=null;
		} // allows repaint() of graph to proceed
		repaint();
	}


	public Image offscreen;
	Dimension offscreensize;
	public Graphics offgraphics;
	protected FontMetrics fontmetrics;
	protected int halfChar; // half the height of font, i.e. fm.getAscent()/2
	Node pick;
	boolean displayNodes=true;

	/**
	 * Alters the flag that controls whether nodes will be displayed. In the
	 * Coloring game, a player was prevented from seeing anyone's color before
	 * shhe had committed to one for himrself.
	 */
	public void setDisplayNodes(boolean flag) {
		displayNodes=flag;
	}

	boolean stress;

	final Color fixedColor=Color.red;
	final Color selectColor=Color.pink;
	final Color nodeColor=new Color(250,220,100);
	final Color stressColor=Color.gray;
	final Color arcColor=Color.red;

	/**
	 * Performs the actual painting of a node.
	 * 
	 * @param g
	 *          the Graphics context in which to paint.
	 * @param n
	 *          the Node to draw
	 * @param render
	 *          the ColorMap to use in selecting choosing a color for the node
	 *          TODO: consider eliminating the ColorMap parameter; it is local
	 *          anyway!
	 */
	protected void paintNode(Graphics g,Node n,ColorMap render) {
		int x=(int)n.x;
		int y=(int)n.y;
		int w=2*nodeRadius; // fm.stringWidth(n.lbl) + 10;
		int h=2*nodeRadius; // fm.getHeight() + 4;
		if(creating) g.setColor((n==pick)?selectColor:(n.fixed?fixedColor:nodeColor));
		else {
			int clr=displayNodes?render.map(n.color):0;
			g.setColor(swatches[clr].color);
		}
		g.fillOval(x-w/2,y-h/2,w,h);
		g.setColor(Color.black);
		g.drawOval(x-w/2,y-h/2,w,h);
		// g.drawString("s"+n.getSweety(),x-10,y+nodeRadius+fm.getAscent());
		String lab="";
		if(!graph.partial()) lab=n.lbl;
		else if(n==graph.eigenNode) lab="you";
		else if(!displayNodes) lab="locked";
		else if(graph.isNeighborDegreeOn()) lab=Integer.toString(n.degree());
		else if(graph.isPlusLabelingOn()) lab="+"+Integer.toString(n.otherEdges);

		g.drawString(lab,x-fontmetrics.stringWidth(lab)/2,y+halfChar);
		//else if(!displayNodes) g.drawString("locked",x-20,y+halfChar);
		//else // if(n.otherEdges>0)
		//g.drawString(Integer.toString(n.degree()),x-fontmetrics.stringWidth(Integer.toString(n.degree()))/2,y+halfChar);
	}

	protected int translateX=0,translateY=0;

	protected Dimension window;

	/** Used by AWT to paint the whole Graph */
	public synchronized void paint(Graphics g) {
		if(!ready(g)) return;
		drawEdges();
		drawNodes();
		commitImage(g);
	}

	/**
	 * Either draws a temporary graphic while waiting for the Graph to be
	 * initialized, or prepares the Graphics context called offgraphics and
	 * concomittant variables. Whenever the panel is resized, offgraphics and
	 * mechanism for translating the center point must be re-initialized.
	 * 
	 * @return a flag saying whether the resident graph is fully ready to be
	 *         drawn.
	 */
	public boolean ready(Graphics g) {
		window=getSize();
		if(waitMessage!=null) {
			g.setColor(getBackground());
			g.fillRect(0,0,window.width,window.height);
			g.setColor(Color.black);
			// System.err.println("GraphPanel paint: "+ waitMessage);
			g.drawString(waitMessage,20,50);
			return false;
		}
		// System.err.println("gPanel paint "+ window +" graph="+ graph);
		if((offscreen==null)||(window.width!=offscreensize.width)||(window.height!=offscreensize.height)) {
			offscreen=createImage(window.width,window.height);
			offscreensize=window;
			offgraphics=offscreen.getGraphics();
			offgraphics.setFont(FONT);
			fontmetrics=offgraphics.getFontMetrics();
			halfChar=fontmetrics.getAscent()/2;
			translateX=window.width/2;
			translateY=window.height/2;
			offgraphics.translate(translateX,translateY);
			// System.err.println("clipbounds "+g.getClipBounds());
		}
		// TODO: move these 2 lines from drawEdges into here.
		// offgraphics.setColor(getBackground());
		// offgraphics.fillRect(-translateX, -translateY, window.width,
		// window.height);
		return true;
	}

	/** a utility called by paint() to draw all edges in the graph */
	public void drawEdges() {
		// TODO: can this be made private?
		offgraphics.setColor(getBackground());
		offgraphics.fillRect(-translateX,-translateY,window.width,window.height);
		Enumeration<Edge> eno=graph.enumEdges();
		while(eno.hasMoreElements()) {
			Edge e=eno.nextElement();
			int x1=(int)e.from.x;
			int y1=(int)e.from.y;
			int x2=(int)e.to.x;
			int y2=(int)e.to.y;
			offgraphics.setColor(arcColor);
			if(graph.partial()&&(e.from==graph.eigenNode||e.to==graph.eigenNode)) offgraphics.setColor(Color.BLACK);
			offgraphics.drawLine(x1,y1,x2,y2);
			// System.err.println("edge from "+ e.from +" to "+ e.to
			// +"=="+e.from.getSweety()+"?");
			// if (e.from.getSweety()==e.to) paintLust(offgraphics,e.from,e.to,100.);
			// if (e.to.getSweety()==e.from) paintLust(offgraphics,e.to,e.from,100.);
			if(stress) {
				// find how much the edge length is different from the desired length
				int len=(int)Math.abs(Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))-e.len);
				String lbl=String.valueOf(len);
				offgraphics.setColor(stressColor);
				offgraphics.drawString(lbl,x1+(x2-x1)/2,y1+(y2-y1)/2);
			}
		}
	}

	/** a utility called by paint() to draw all nodes in the graph */
	public void drawNodes() {
		// TODO: can this be made private?
		Enumeration<Node> eno=graph.enumNodes();
		while(eno.hasMoreElements()) {
			Node n=eno.nextElement();
			paintNode(offgraphics,n,render);
			// System.err.print(" "+n.color);
		}
		// System.err.println(" <== colors");
	}

	/** a utility to transfer all the graphics in offscreen to the visible screen. */
	public void commitImage(Graphics g) {
		g.drawImage(offscreen,0,0,null);
	}

	/**
	 * converts a color name (a String) to its index in swatches. WARNING: This
	 * may be moved to the ColorMap class. TODO
	 */
	public static int intCode(String colorName) {
		int code=0;
		while(!swatches[code].name.equals(colorName))
			code++;
		return code;
	}

	/**
	 * A structure for keeping names and colors together. TODO: move this into
	 * ColorMap?
	 */
	public static final Swatch[] swatches= {
		new Swatch("white",	Color.WHITE),
		new Swatch("yellow",Color.YELLOW),
		new Swatch("red",		Color.RED),
		new Swatch("green",	Color.GREEN),
		new Swatch("blue",	new Color(75,75,255)),
		new Swatch("brown",	new Color(180,90,0)),
		new Swatch("orange",new Color(255,120,0)),
		new Swatch("cyan",	Color.CYAN),
		new Swatch("pink",	Color.PINK),
		new Swatch("violet",Color.MAGENTA),
		new Swatch("gray",	Color.LIGHT_GRAY)};

	/**
	 * A structure for keeping names and colors together. TODO: move this into
	 * ColorMap?
	 */
	public static class Swatch {
		public String name;

		public Color color;

		public Swatch(String s,Color c) {
			name=s;
			color=c;
		}
	}

}// EOClass GraphPanel
